/**
 *  @filename   :   epd2in13.cpp
 *  @brief      :   Implements for e-paper library
 *  @author     :   Yehui from Waveshare
 *
 *  Copyright (C) Waveshare     September 9 2017
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documnetation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to  whom the Software is
 * furished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#include <stdlib.h>
#include "epd2in13.h"
#include "sdkconfig.h"

static const char *TAG = "edp";

#define LCD_EPAPER_CHECK(a, str)  if(!(a)) {                               \
        ESP_LOGE(TAG,"%s:%d (%s):%s", __FILE__, __LINE__, __FUNCTION__, str);   \
                                                                   \
    }
    
#if 1//CONFIG_EPD_SCREEN_2IN13

// EPD2IN13 commands
#define DRIVER_OUTPUT_CONTROL                       0x01
#define BOOSTER_SOFT_START_CONTROL                  0x0C
#define GATE_SCAN_START_POSITION                    0x0F
#define DEEP_SLEEP_MODE                             0x10
#define DATA_ENTRY_MODE_SETTING                     0x11
#define SW_RESET                                    0x12 //OK
#define TEMPERATURE_SENSOR_CONTROL                  0x1A
#define MASTER_ACTIVATION                           0x20
#define DISPLAY_UPDATE_CONTROL_1                    0x21
#define DISPLAY_UPDATE_CONTROL_2                    0x22
#define WRITE_RAM_BW                                0x24
#define WRITE_RAM                                   0x24
#define WRITE_RAM_RED                               0x26
#define WRITE_VCOM_REGISTER                         0x2C
#define WRITE_LUT_REGISTER                          0x32
#define SET_DUMMY_LINE_PERIOD                       0x3A
#define SET_GATE_TIME                               0x3B
#define BORDER_WAVEFORM_CONTROL                     0x3C
#define SET_RAM_X_ADDRESS_START_END_POSITION        0x44
#define SET_RAM_Y_ADDRESS_START_END_POSITION        0x45
#define SET_RAM_X_ADDRESS_COUNTER                   0x4E
#define SET_RAM_Y_ADDRESS_COUNTER                   0x4F
#define TERMINATE_FRAME_READ_WRITE                  0xFF

static const unsigned char EPD_2IN13_lut_full_update[] = {
    0x80,0x60,0x40,0x00,0x00,0x00,0x00,             //LUT0: BB:     VS 0 ~7
    0x10,0x60,0x20,0x00,0x00,0x00,0x00,             //LUT1: BW:     VS 0 ~7
    0x80,0x60,0x40,0x00,0x00,0x00,0x00,             //LUT2: WB:     VS 0 ~7
    0x10,0x60,0x20,0x00,0x00,0x00,0x00,             //LUT3: WW:     VS 0 ~7
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,             //LUT4: VCOM:   VS 0 ~7

    0x03,0x03,0x00,0x00,0x02,                       // TP0 A~D RP0
    0x09,0x09,0x00,0x00,0x02,                       // TP1 A~D RP1
    0x03,0x03,0x00,0x00,0x02,                       // TP2 A~D RP2
    0x00,0x00,0x00,0x00,0x00,                       // TP3 A~D RP3
    0x00,0x00,0x00,0x00,0x00,                       // TP4 A~D RP4
    0x00,0x00,0x00,0x00,0x00,                       // TP5 A~D RP5
    0x00,0x00,0x00,0x00,0x00,                       // TP6 A~D RP6

//	0x15,0x41,0xA8,0x32,0x30,0x0A,//[70:75]
    0x17,0x41,0xAC,0x32,0x02,0x0D,
};

static const unsigned char EPD_2IN13_lut_partial_update[] = {
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,             //LUT0: BB:     VS 0 ~7
    0x80,0x00,0x00,0x00,0x00,0x00,0x00,             //LUT1: BW:     VS 0 ~7
    0x40,0x00,0x00,0x00,0x00,0x00,0x00,             //LUT2: WB:     VS 0 ~7
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,             //LUT3: WW:     VS 0 ~7
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,             //LUT4: VCOM:   VS 0 ~7

    0x0A,0x00,0x00,0x00,0x00,                       // TP0 A~D RP0
    0x00,0x00,0x00,0x00,0x00,                       // TP1 A~D RP1
    0x00,0x00,0x00,0x00,0x00,                       // TP2 A~D RP2
    0x00,0x00,0x00,0x00,0x00,                       // TP3 A~D RP3
    0x00,0x00,0x00,0x00,0x00,                       // TP4 A~D RP4
    0x00,0x00,0x00,0x00,0x00,                       // TP5 A~D RP5
    0x00,0x00,0x00,0x00,0x00,                       // TP6 A~D RP6

    0x15,0x41,0xA8,0x32,0x30,0x0A,
};

static uint16_t g_width = EPD_WIDTH;
static uint16_t g_height = EPD_HEIGHT;
static uint8_t is_init_iface = 0;
static uint8_t g_mode = EPD_2IN13_FULL;

static void SendCommand(unsigned char command) {
    DigitalWrite(DC_PIN, LOW);
    SpiTransfer(command);
}

static void SendData(unsigned char data) {
    DigitalWrite(DC_PIN, HIGH);
    SpiTransfer(data);
}

static void WaitUntilIdle(void) {
    uint16_t timeout=50;
    while(DigitalRead(BUSY_PIN) == HIGH) {      //LOW: idle, HIGH: busy
        DelayMs(100);
        if(--timeout == 0){
            ESP_LOGW(TAG, "Wait epaper idle timeout");
            break;
        }
    }
}

static void Epd_Set_LUT(uint8_t Mode)
{
    g_mode = Mode;
    SendCommand(0x32);
    if(Mode == EPD_2IN13_FULL) {
        for (int i = 0; i < 70; i++) {
            SendData(EPD_2IN13_lut_full_update[i]);
        }
    } else if(Mode == EPD_2IN13_PART) {
        for (int i = 0; i < 70; i++) {
            SendData(EPD_2IN13_lut_partial_update[i]);
        }
    }

}


int Epd_Init(uint8_t Mode) {
    uint8_t count;
    ESP_LOGI(TAG, "e-Paper init ");
    /* this calls the peripheral hardware interface, see epdif */
    if (0 == is_init_iface) {
      if (IfInit() != 0) {
         ESP_LOGE(TAG, "ifinit failed");
         return -1;
      }
      is_init_iface = 1;
    }

    /* EPD hardware init start */
    DigitalWrite(RST_PIN, HIGH);
    DelayMs(100);
    DigitalWrite(RST_PIN, LOW);            //module reset
    DelayMs(100);
    DigitalWrite(RST_PIN, HIGH);
    DelayMs(100);
    if(Mode == EPD_2IN13_FULL) {
	    WaitUntilIdle();
	    SendCommand(0x12); // soft reset
	    WaitUntilIdle();

	    SendCommand(0x74); //set analog block control
        SendData(0x54);
        SendCommand(0x7E); //set digital block control
        SendData(0x3B);

/*
        SendCommand(0x01); //Driver output control
        SendData(0xF9);
//        SendData(0x97); //A[7:0]
        SendData(0x00);//A[8]
        SendData(0x00);//B[2:0]
*/

        SendCommand(DRIVER_OUTPUT_CONTROL);
        SendData((EPD_2IN13_HEIGHT -1) & 0xFF);
        SendData(((EPD_2IN13_HEIGHT-1)>> 8) & 0xFF);
        SendData(0x00);

			/*
			A [1:0]
			00 –Y decrement, X decrement,
			01 –Y decrement, X increment,
			-----------------------------
			10 –Y increment, X decrement,
			11 –Y increment, X increment [POR]

			A[2]Set the direction in which the address counter is updated automatically after data
					are written to the RAM.
			AM= 0, the address counter is updated in the X direction. [POR]
			--------------------------------------------------------------
			AM = 1, the address counter is updated in the Y direction.
			*/
        SendCommand(DATA_ENTRY_MODE_SETTING); //data entry mode A[2:0]
        SendData(0x03);

        SendCommand(SET_RAM_X_ADDRESS_START_END_POSITION); //set Ram-X address start/end position
		SendData(0x00); //A[4:0] X Start, POR = 00h
      //SendData(0x0F);    //0x0C-->(12+1)*8=128
		SendData(0x0C);//B[4:0]  X End, POR = 0Ch

        SendCommand(SET_RAM_Y_ADDRESS_START_END_POSITION); //set Ram-Y address start/end position
      // SendData(0xF9);   //0xF9-->(249+1)=250
		SendData(0x00);//A[8:0] Y Start, POR = 00D3h  (211)  0xd3-->(211+1)=212
        SendData(0x00);
        SendData(0xD3);//B[8:0] Y End, POR = 0000h
        SendData(0x00);

        SendCommand(0x3C); //BorderWavefrom  Select border waveform for VBD
        SendData(0x03);
      // SendData(0x01); //lut1

        SendCommand(0x2C); //VCOM Voltage
//        SendData(0x55); //0x5a
        SendData(0x5A); //0x5a

/*SetGate Driving voltage

A[4:0]=17h[POR],VGH at 20V[POR]
VGH setting from 10V to 21V
*/
        SendCommand(0x03); //Gate Driving voltage control
        SendData(EPD_2IN13_lut_full_update[70]); //0x17
/*
SetSource Driving voltage
A[7:0]= 41h[POR],VSH1 at 15V
B[7:0]=ACh[POR],VSH2 at 5.4V
C[7:0]= 32h[POR], VSL at -15V
*/
        SendCommand(0x04); //Source Driving voltage control
        SendData(EPD_2IN13_lut_full_update[71]);
        SendData(EPD_2IN13_lut_full_update[72]);
        SendData(EPD_2IN13_lut_full_update[73]);

/*
Set A[6:0]=02h
Default value will give 50Hz
Frame frequency
*/
        SendCommand(0x3A);     //Set dummy line period
        SendData(EPD_2IN13_lut_full_update[74]);
        SendCommand(0x3B);     //Set Gate line width
        SendData(EPD_2IN13_lut_full_update[75]);

/*
Write LUT register
*/
        Epd_Set_LUT(Mode);

        SendCommand(0x4E);   // set RAM x address count to 0;
        SendData(0x00);
        SendCommand(0x4F);   // set RAM y address count to 211;
     //   SendData(0xD3);
	    SendData(0x00);

        SendData(0x00);
        WaitUntilIdle();


	  }
    else if(Mode == EPD_2IN13_PART){
			 SendCommand(0x2C);     //VCOM Voltage
        SendData(0x26);

        WaitUntilIdle();

        Epd_Set_LUT(Mode);

        SendCommand(0x37);
        SendData(0x00);
        SendData(0x00);
        SendData(0x00);
        SendData(0x00);
        SendData(0x40);
        SendData(0x00);
        SendData(0x00);

        SendCommand(0x22);
        SendData(0xC0);

        SendCommand(0x20);
        WaitUntilIdle();

        SendCommand(0x3C); //BorderWavefrom
        SendData(0x01);
    } else {
        ESP_LOGE(TAG,"error, the Mode is EPD_2IN13_FULL or EPD_2IN13_PART");
    }
    return 0;
 }


int Epd_Initi2(uint8_t Mode) {
    ESP_LOGI(TAG, "e-Paper init ");
    /* this calls the peripheral hardware interface, see epdif */
    if (0 == is_init_iface) {
        if (IfInit() != 0) {
            ESP_LOGE(TAG, "ifinit failed");
            return -1;
        }
        is_init_iface = 1;
    }

    /* EPD hardware init start */
    DigitalWrite(RST_PIN, HIGH);
    DelayMs(100);
    DigitalWrite(RST_PIN, LOW);                //module reset    
    DelayMs(100);
    DigitalWrite(RST_PIN, HIGH);
    DelayMs(100);

    ESP_LOGI(TAG,"reset.");

    WaitUntilIdle();
    SendCommand(0x12); // soft reset
    WaitUntilIdle();
    
    SendCommand(0x03); //Gate Driving voltage Control
    SendData(0x19);

    SendCommand(0x04); //Source Driving voltage Control
    SendData(0x3c);
    SendData(0xA8);
    SendData(0x2e);

    SendCommand(DRIVER_OUTPUT_CONTROL);
    SendData((EPD_2IN13_HEIGHT - 1) & 0xFF);
    SendData(((EPD_2IN13_HEIGHT - 1) >> 8) & 0xFF);
    SendData(0x00);                     // GD = 0; SM = 0; TB = 0;

    // SendCommand(BOOSTER_SOFT_START_CONTROL);
    // SendData(0xD7);
    // SendData(0xD6);
    // SendData(0x9D);
    SendCommand(WRITE_VCOM_REGISTER);
    SendData(0xA8);                     // VCOM 7C
    SendCommand(SET_DUMMY_LINE_PERIOD);
    SendData(0x1A);                     // 4 dummy lines per gate
    SendCommand(SET_GATE_TIME);
    SendData(0x08);                     // 2us per line

    SendCommand(0X3C);	// BORDER_WAVEFORM_CONTROL
    SendData(0x63);

    SendCommand(DATA_ENTRY_MODE_SETTING);
    SendData(0x03);                     // X increment; Y increment

    // //set the look-up table register
    Epd_Set_LUT(Mode);

    return 0;
}



int Epd_Deinit(void) {
    ESP_LOGI(TAG, "e-Paper deinit ");
    if (IfDeinit() != 0) {
        ESP_LOGE(TAG, "ifdeinit failed");
        return -1;
    }
    
    return 0;
}



/**
 *  @brief: clear the frame memory with the specified color.
 *          this won't update the display.
 */
void Epd_ClearFrameMemory(unsigned char color) {
    
}

void Epd_set_window(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1)
{

    LCD_EPAPER_CHECK((0 == (x0 % 8)), "x0 should be a multiple of 8");
    LCD_EPAPER_CHECK((0 == ((x1 + 1) % 8)), "x1+1 should be a multiple of 8");

    x0 >>= 3;
    x1 = ((x1 + 1) >> 3) - 1;
    ESP_LOGI(TAG, "window:[%d, %d, %d, %d]", x0, y0, x1, y1);

    SendCommand(SET_RAM_X_ADDRESS_START_END_POSITION);
    /* x point must be the multiple of 8 or the last 3 bits will be ignored */
    SendData(x0 & 0xFF);ESP_LOGI(TAG, "0x44, 0x%x, 0x%x", (x0 ) & 0xFF, (x1 ) & 0xFF);
    SendData(x1 & 0xFF);
    SendCommand(SET_RAM_Y_ADDRESS_START_END_POSITION);
    SendData(y0 & 0xFF);
    SendData((y0 >> 8) & 0xFF);
    SendData(y1 & 0xFF);
    SendData((y1 >> 8) & 0xFF);
    ESP_LOGI(TAG, "0x45, 0x%x, 0x%x, 0x%x, 0x%x", y0 & 0xFF, (y0 >> 8) & 0xFF, y1 & 0xFF, (y1 >> 8) & 0xFF);

    SendCommand(SET_RAM_X_ADDRESS_COUNTER);
    /* x point must be the multiple of 8 or the last 3 bits will be ignored */
    SendData(x0 & 0xFF);
    SendCommand(SET_RAM_Y_ADDRESS_COUNTER);
    SendData(y0 & 0xFF);
    SendData((y0 >> 8) & 0xFF);
    ESP_LOGI(TAG, "0x4E, 0x%x", (x0 ) & 0xFF);
    ESP_LOGI(TAG, "0x4F, 0x%x, 0x%x", y0 & 0xFF, (y0 >> 8) & 0xFF);
    
    SendCommand(WRITE_RAM);
}

/**
 *  @brief: private function to specify the memory area for data R/W
 */
static void Epd_SetMemoryArea(int x_start, int y_start, int x_end, int y_end) {
    SendCommand(SET_RAM_X_ADDRESS_START_END_POSITION);
    /* x point must be the multiple of 8 or the last 3 bits will be ignored */
    SendData((x_start >> 3) & 0xFF);
    SendData((x_end >> 3) & 0xFF);
    SendCommand(SET_RAM_Y_ADDRESS_START_END_POSITION);
    SendData(y_start & 0xFF);
    SendData((y_start >> 8) & 0xFF);
    SendData(y_end & 0xFF);
    SendData((y_end >> 8) & 0xFF);
}

/**
 *  @brief: private function to specify the start point for data R/W
 */
static void Epd_SetMemoryPointer(int x, int y) {
    SendCommand(SET_RAM_X_ADDRESS_COUNTER);
    /* x point must be the multiple of 8 or the last 3 bits will be ignored */
    SendData((x >> 3) & 0xFF);
    SendCommand(SET_RAM_Y_ADDRESS_COUNTER);
    SendData(y & 0xFF);
    SendData((y >> 8) & 0xFF);
    WaitUntilIdle();
}

void Epd_draw_bitmap(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t *bitmap)
{
    uint8_t *p = (uint8_t *)bitmap;
    
    // Epd_set_window(x, y, x + w - 1, y + h - 1);
    // uint32_t len = w / 8 * h;
    // ESP_LOGI(TAG, "len = %d\n", len);
    // for (uint32_t i = 0; i < len; i++) {
    //     SendData(p[i]);
    // }
    int x_end = x + w - 1;
    int y_end = y + h - 1;
    Epd_SetMemoryArea(x, y, x_end, y_end);
    /* set the frame memory line by line */
    for (int j = y; j <= y_end; j++) {
        Epd_SetMemoryPointer(x, j);
        SendCommand(WRITE_RAM);
        for (int i = x / 8; i <= x_end / 8; i++) {
            SendData(p[(i - x / 8) + (j - y) * (w / 8)]);
        }
    }
}

/**
 *  @brief: update the display
 *          there are 2 memory areas embedded in the e-paper display
 *          but once this function is called,
 *          the the next action of SetFrameMemory or ClearFrame will 
 *          set the other memory area.
 */
void Epd_DisplayFrame(void)
{
    if (EPD_2IN13_FULL == g_mode) {
        SendCommand(0x22); //Display Update Control
        SendData(0xC7);
        SendCommand(0x20);  //Activate Display Update Sequence

    } else {
        SendCommand(0x22); //Display Update Control
        SendData(0x0C);
        SendCommand(0x20); //Activate Display Update Sequence
    }
    WaitUntilIdle();
}



/**
 *  @brief: After this command is transmitted, the chip would enter the 
 *          deep-sleep mode to save power. 
 *          The deep sleep mode would return to standby by hardware reset. 
 *          You can use Epd_Init() to awaken
 */
void Epd_DeepSleep(void)
{
    SendCommand(0x10); //enter deep sleep
    SendData(0x01);
    DelayMs(100);
}



static void Epd_TurnOnDisplay(void)
{
    SendCommand(0x22);//Display Update Sequence Option: Enable the stage for Master Activation
    SendData(0xC7);
    SendCommand(0x20);//Activate Display Update Sequence
    WaitUntilIdle();

}



void Epd_Clear()
{
    int Width, Height;
    Width = (g_width % 8 == 0)? (g_width / 8 ): (g_width / 8 + 1);
    Height = g_height;

//    EPD_2IN13_V2_SendCommand(0x24);//Write RAM(BW)  Write pixel:1,r Black pixel:0
//    for (UWORD j = 0; j < Height; j++) {
//        for (UWORD i = 0; i < Width; i++) {
//            EPD_2IN13_V2_SendData(0XFF);
//        }
//    }
		
        SendCommand(0x4E);   // set RAM x address count to 0;
        SendData(0x00);
        SendCommand(0x4F);   // set RAM y address count to 211;
        //EPD_2IN13_V2_SendData(0xF9);
		SendData(0x00);

        SendData(0x00);
        WaitUntilIdle();
		
		SendCommand(0x24);//
    for (int j = 0; j < Height; j++) {
        for (int i = 0; i < Width; i++) {
					//EPD_2IN13_V2_SendData((j%8)?0XFE:0X00);
				//	printf("i:%d,k:%d ",i,k);
					SendData(0XFF);
        }
				//printf("\nH:%d\n",j);
    }
		
    Epd_TurnOnDisplay();
}

void Epd_Draw_Grid(void)
{
    int Width, Height;
    Width = (g_width % 8 == 0)? (g_width / 8 ): (g_width / 8 + 1);
    Height = g_height;

	SendCommand(0x4E);   // set RAM x address count to 0;
	SendData(0x00);
	SendCommand(0x4F);   // set RAM y address count to 211;
		//EPD_2IN13_V2_SendData(0xF9);
	SendData(0x00);

	SendData(0x00);
    WaitUntilIdle();
    SendCommand(0x24);//
    for (int j = 0; j < Height; j++) {
        for (int i = 0; i < Width; i++) {
	        SendData((j%8)?0XFE:0X00);
				//	ESP_LOGI(TAG,"i:%d,k:%d ",i,k);
					//EPD_2IN13_V2_SendData(0XFF);
        }
				//ESP_LOGI(TAG,"\nH:%d\n",j);
    }

    Epd_TurnOnDisplay();
}
#endif

/* END OF FILE */


